function Bilibili2Ass() {
    this.lines = [];
    this.tracks = [];
    this.width = 3840; // 设置脚本宽度
    this.height = 2160; // 设置脚本高度
    this.track_count = 20; // 设置字幕纵向轨道数量
    this.track_height = Math.ceil(this.height / this.track_count); // 计算字幕轨道高度
    this.track_interval_time = 0.5; // 同轨道字幕间隙时间
    
    // 初始化轨道数据
    for(let i = 0; i < this.track_count; i++) {
        this.tracks.push({
            id: i,
            end: 0
        })
    }
    this.loadFile = file => {
        return new Promise(resolve => {
            let reader = new FileReader();
            reader.onload = e => {
                this.lines = this.parseFile(reader.result);
                resolve();
            }
            reader.readAsText(file);
        })
    }
    this.parseFile = content => {
        content = content.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, '');
        return this.parseXml(content);
    }
    this.parseXml = content => {
        let data = (new DOMParser()).parseFromString(content, 'text/xml');
        return Array.apply(Array, data.querySelectorAll('d')).map(line => {
            let info = line.getAttribute('p').split(',');
            console.log('info', info)
            let text = line.textContent;
            info.strlen = text.length;
            let start = Number(info[0]);
            let end = start + 8;
            let start_time = this.formatTime(start);
            let end_time = this.formatTime(end);
            let style = 'Default';
            return {
                text,
                start,
                end,
                start_time,
                end_time,
                style,
                info
            }
        })
    } 
    /**
    * 颜色的数值化为十六进制字符串表示
    * @param String color 
    * @returns String
    */
    this.rrggbb = color => {
        let t = Number(color).toString(16).toUpperCase();
        return ('000000' + t).slice(-6);
    }
    this.formatTime = time => {
        time = Math.round(time * 100);
        let hour = (Math.floor(time / 360000)).toString().padStart(2, 0);
        time %= 360000;
        let minute = (Math.floor(time / 6000)).toString().padStart(2, 0);
        time %= 6000;
        let second = (Math.floor(time / 100)).toString().padStart(2, 0);
        time %= 100;
        let ms = time;
        return `${hour}:${minute}:${second}.${ms}`;
    }
    
    this.toAss = () => {
        let header =
`[Script Info]
; Script generated by Aegisub 3.2.2
; http://www.aegisub.org/
Title: Default Aegisub file
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.601
PlayResX: 3840
PlayResY: 2160

[Aegisub Project Garbage]
Last Style Storage: Default
Video AR Mode: 4
Video AR Value: 1.777778
Video Zoom Percent: 0.125000
Scroll Position: 19
Active Line: 43
Video Position: 6555

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,黑体,100,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,1,0.1,0.1,2,10,10,10,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
`;
        return header + this.lines.map(this.convertDialog).join("\n");
    }
    this.convertDialog = line => {
        this.getEffect(line);
        return `Dialogue: 0,${line.start_time},${line.end_time},${line.style},,20,20,2,,${line.effect}${line.text}`;
    }
    this.getEffect = line => {
        let info = line.info;
        let getPos = () => {
            let sel = {
                0: 'R2L',
                1: 'R2L',
                2: 'R2L',
                3: 'R2L',
                4: 'BOTTOM',
                5: 'TOP'
            }
            let pos_type = sel[Number(info[1])]; // 从弹幕中获取字幕位置类型
            let result = '';
            this.tracks = this.tracks.sort((x, y) => {
                return x.end < y.end ? -1 : 1;
            })
            let track;
            if (pos_type == 'R2L') {
                track = this.tracks.filter(x => x.id < (this.track_count * 2 / 3))[0];
            } else if (pos_type == 'TOP') {
                track = this.tracks.filter(x => x.id < this.track_count / 2)[0];
            } else {
                track = this.tracks.filter(x => x.id > this.track_count / 2)[0];
            }
        
            line.width = line.text.length * 15;
            let y = (track.id + 1) * this.track_height;
            if (pos_type == 'R2L') {
                if (line.start < track.end) {
                    line.start = track.end + this.track_interval_time;
                }
                let path = [this.width + line.width, y, -line.width, y].join(',');
                result = `\\move(${path})`;
                track.end = line.start + Math.floor(line.width / this.width);
            } else {
                if (line.start < track.end) {
                    line.start = track.end + this.track_interval_time;
                }
                let x = this.width / 2 - line.width / 2;
                result = `\\pos(${x}, ${y})`;
                track.end = line.start + 8;
            }
            return result;
        }

        let getColor = () => {
            let main_color = this.rrggbb(parseInt(info[3], 10));
            let color = [
                {
                    key: '1c',
                    value: `\&H${main_color}&`
                },
                {
                    key: '4c',
                    value: '\&H000000&'
                }
            ]
            let arr = [];
            color.forEach(x => {
                arr.push(`\\${x.key}${x.value}`);
            });
            return arr.join('');
        }

        let effects = [];
        let fileters = [getPos, getColor];
        fileters.forEach(filter => {
            let effect = filter(info);
            if (effect) {
                effects.push(effect);
            }
        })

        if (effects.length) {
            line.effect = `{${effects.join('')}}`;
        }
    }
    return this;
}